package vip.xls8z.learning.nio.reactor.multiple.handler;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class Handler implements Runnable {
    final Selector selector;
    final SocketChannel socketChannel;
    SelectionKey selectionKey;

    ByteBuffer inputBuff = ByteBuffer.allocate(1024);
    ByteBuffer outputBuff = ByteBuffer.allocate(1024);

    static ExecutorService executorService = Executors.newFixedThreadPool(4);

    public Handler(Selector selector, SocketChannel socketChannel) throws ClosedChannelException {
        this.selector = selector;
        this.socketChannel = socketChannel;

        selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
        selectionKey.attach(this);
        selector.wakeup();
    }

    @Override
    public void run() {
        try {
            if (selectionKey.isReadable()) {
                read();
            }

            if (selectionKey.isWritable()) {
                send();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void send() throws IOException {
        outputBuff.clear();
        String message = "Hello, Client. " + UUID.randomUUID();
        outputBuff.put(message.getBytes(StandardCharsets.UTF_8));
        outputBuff.flip();
        socketChannel.write(outputBuff);
        selectionKey.interestOps(SelectionKey.OP_READ);
        selectionKey.selector().wakeup();

        executorService.submit(new Processor(() -> {
            // do something, example: decode/compute/encode/other business
            log.info("write message: {}", message);
        }));
    }

    private void read() throws IOException {
        inputBuff.clear();
        int length = socketChannel.read(inputBuff);
        if (length == -1) {
            socketChannel.close();
            selectionKey.cancel();
            return;
        }
        selectionKey.interestOps(SelectionKey.OP_WRITE);
        selectionKey.selector().wakeup();

        executorService.submit(new Processor(() -> {
            // do something, example: decode/compute/encode/other business
            inputBuff.flip();
            String message = new String(inputBuff.array(), 0, length);
            log.info("read message: {}", message);
        }));
    }

    public static class Processor implements Runnable {
        private final Callback callback;

        public Processor(Callback callback) {
            this.callback = callback;
        }

        @Override
        public void run() {
            try {
                doProcessor();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private synchronized void doProcessor() throws Exception {
            callback.process();
        }

        public interface Callback {
            void process() throws IOException;
        }
    }
}
